
const fs = require('fs');
const zlib = require('zlib');
const EventEmitter = require('events').EventEmitter;

class PNGLoader extends EventEmitter{

    constructor(path){
        //要先执行这个方法
        super();

        if(!path){
            this.emit('fail');
            return;
        }

        this.path = path;
        this.stream = fs.createReadStream(this.path);

        //使用paused模式
        this.stream.pause();
        this.stream.once('readable', ()=>{
            this.checkSignature();
            //读取第一个数据块的名称和长度
            let firstHead = this.readHeadAndLength();
            //从第一个数据块开始解析
            this.readChunk(firstHead);
            //对解压图像数据进行反滤波等操作
            this.solveData();
            this.emit('done');
        });
    }

    /**
     * 验证Signature
     * @returns {boolean}
     */
    checkSignature(){
        //PNG的Signature长度为8字节, 1Byte = 8bit
        let buffer = this.stream.read(8);
        let signature = [137, 80, 78, 71, 13, 10, 26, 10];
        for(let i=0; i<signature.length; i++){
            let v = buffer.readUInt8(i);
            if(v !== signature[i]) throw new Error('It is not PNG file !');
        }

        console.log('It is a PNG file...');
        return true;
    }

    /**
     * 读取数据块的名称和长度
     * Length 和 Name(Chunk type) 位于每个数据块开头
     * Length, Chunk type 各占4bytes
     * @returns {{name: string, length: *}}
     */
    readHeadAndLength(){
        let buffer = this.stream.read(8);
        // 将Length的4bytes读成一个32bits的整数
        let length = buffer.readInt32BE(0);
        let name = buffer.toString(undefined, 4, 8);
        return {name, length};
    }

    /**
     * 递归读取其他数据块
     * @param name
     * @param length
     */
    readChunk({name, length}){
        if(!length || !name){
            console.log(name, length);
            return;
        }

        switch(name){
            case 'IHDR':
                this.readChunk(this.readIHDR(name, length));
                break;
            case 'IDAT':
                this.readChunk(this.readIDAT(name, length));
                break;
            case 'PLTE':
                // 还不支持调色板PLTE数据块
                throw new Error('PLTE');
                break;
            default:
                // 跳过其他数据块
                console.log('Skip',name,length);
                this.stream.read(length+4);
                this.readChunk(this.readHeadAndLength());
        }
    }

    /**
     * IHDR数据块
     * @param name
     * @param length
     * @returns {{name, length}|{name: string, length: *}}
     */
    readIHDR(name, length){
        console.log(name, length);
        if(name !== 'IHDR') throw new Error('IHDR ERROR !');

        this.info = {};
        this.info.width = this.stream.read(4).readInt32BE(0);
        this.info.height = this.stream.read(4).readInt32BE(0);
        this.info.bitDepth = this.stream.read(1).readUInt8(0);
        // color type :
        // 0：灰度图像; 1，2，4，8或16 
        // 2：真彩色图像; 8或16 
        // 3：索引彩色图像; 1，2，4或8 
        // 4：带α通道数据的灰度图像; 8或16 
        // 6：带α通道数据的真彩色图像; 8或16
        this.info.coloType = this.stream.read(1).readUInt8(0);
        this.info.compression = this.stream.read(1).readUInt8(0);
        this.info.filter = this.stream.read(1).readUInt8(0);
        this.info.interlace = this.stream.read(1).readUInt8(0);
        console.log(this.info);

        switch(this.info.coloType){
            case 0:
                this.info.bands = 1;
                break;
            case 2:
                this.info.bands = 3;
                break;
            case 3:
                // 不支持索引色
                throw new Error('Do not support this color type !');
                break;
            case 4:
                this.info.bands = 2;
                break;
            case 6:
                this.info.bands = 4;
                break;
            default:
                throw new Error('Unknown color type !');
        }

        // CRC
        this.stream.read(4);
        return this.readHeadAndLength();
    }

    /**
     * IDAT数据块
     * @param name
     * @param length
     * @returns {{name, length}|{name: string, length: *}}
     */
    readIDAT(name, length){
        console.log('Try to read IDAT, zip length', length);
        if(name !== 'IDAT') throw new Error('IDAT ERROR !');

        let buffer = this.stream.read(length);
        //解压数据块中data部分,得到真正的图像数据
        this.data = zlib.unzipSync(buffer);
        console.log("Unzip length", this.data.length);
        
        // CRC
        this.stream.read(4);
        return this.readHeadAndLength();
    }

    /**
     * 根据具体的滤波方法解析图像数据
     * 这里只支持filterMethod=0的图像
     */
    solveData(){
        console.log(`solve image data, filter method:${this.info.filter}`);
        switch(this.info.filter){
            case 0:
                this.solveDataForNoneFilter();
                break;
            default:
                throw new Error('Do not support this filter method');
        }

    }

    /**
     * 处理filterMethod=0时整个图像
     */
    solveDataForNoneFilter(){
        let colByteLength = this.info.width*this.info.bands*this.info.bitDepth/8+1;

        for(let row=0; row<this.info.height; row++){

            let filterType = this.getFilterType(row);
            let start = row*colByteLength;
            //console.log(`solve row${row}, filter type:${filterType}`);
            for(let i=1; i<colByteLength; i++){
                let index = start+i;
                this.data[index] = this.reconForNoneFilter(index, start, filterType, colByteLength);
            }
        }
    }

    /**
     * 处理filterMethod=0时整个图像中的一行
     * 这时每行都对应一种具体的FilterType
     * @param index
     * @param start
     * @param filterType
     * @param colByteLength
     * @returns {*}
     */
    reconForNoneFilter(index, start, filterType, colByteLength){
        let pixelByteLength = this.info.bands*this.info.bitDepth/8;
        switch(filterType){
            case 0:
                //None
                return this.data[index];
                break;
            case 1:
                //Sub
                if(index-start-1<pixelByteLength)return this.data[index];
                else return this.data[index] + this.data[index-pixelByteLength];
            case 2:
                //Up
                return this.data[index] + this.data[index-colByteLength];
            case 3:
                //Average
                {
                    let a=0,b=0;
                    a = index-start-1<pixelByteLength?a:this.data[index-pixelByteLength];
                    b = this.data[index-colByteLength];
                    return this.data[index] + Math.floor((a+b)/2);
                }
            case 4:
                //Paeth
                {
                    let a=0,b=0,c=0;
                    b = this.data[index-colByteLength];
                    if(index-start-1<pixelByteLength){
                        a = c =0;
                    }else{
                        a = this.data[index-pixelByteLength];
                        if(start>=colByteLength){
                            c = this.data[index-pixelByteLength-colByteLength];
                        }
                    }
                    //PaethPredictor function
                    let p = a + b - c;
                    let pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c);
                    let Pr = 0;
                    if(pa <= pb && pa <= pc)Pr = a;
                    else if(pb <= pc)Pr = b;
                    else Pr = c;

                    return Pr;
                }
            default:
                throw new Error('recon failed');
        }
    }

    /**
     * 获得某个像素的值
     * 这里只支持非隔行扫描
     * @param row
     * @param col
     * @returns {Array}
     */
    getPixel(row, col){
        let res = [];
        let offset = this.info.bitDepth/8;
        switch(this.info.interlace){
            case 0:
                // 非隔行扫描： 每行有个1字节长度的filterType标识，所以加上row+1
                let pointer = (row*this.info.width + col) * offset * this.info.bands+(row+1);
                for(let b=0;b<this.info.bands;b++){
                    res.push(this.readNum(this.data, pointer+offset*b));
                }
                break;
            default:
                throw new Error('Wrong interlace !');
        }
        return res;
    }

    /**
     * 获取每行的filter type
     * 每行有个1字节长度的filterType
     * @param row
     * @returns {*}
     */
    getFilterType(row){
        let offset = this.info.bitDepth/8;
        let pointer = row * this.info.width * offset * this.info.bands + row;
        return this.readNum(this.data, pointer, 8);
    }

    /**
     * buffer转number
     * @param buffer
     * @param pointer
     * @param depth
     * @returns {number}
     */
    readNum(buffer, pointer, depth = this.info.bitDepth){
        let num = 0;
        switch(depth){
            case 8:
                num = buffer.readUInt8(pointer);
                break;
            default:
                throw new Error('Wrong depth !');
        }
        return num;
    }

    printData(){
        for(let row=0;row<this.info.height;row++){
            // filter具体类型位于每行开头
            let filter = this.getFilterType(row);
            console.log(`------Row${row}------`);
            console.log(`Filter type:${filter}`);
            for(let col=0;col<this.info.width;col++){
                console.log(this.getPixel(row, col, filter));
            }
        }
    }
}

module.exports = PNGLoader;

